package com.cheng.gmall.item.service.impl;

import com.cheng.gmall.item.service.LockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author Cheng-02
 * @version V1.0
 * @Package com.cheng.gmall.item.service.impl
 * @date 2024/5/31 下午6:02
 */
@Service
public class LockServiceImpl implements LockService {
    @Autowired
    StringRedisTemplate redisTemplate;

    /*
     * 阻塞式加锁
     * 1.如果获取锁失败就一直等待，直到加锁成功
     *
     */
    @Override
    public String lock() throws InterruptedException {
        // 尝试在 Redis 中设置 "lock" 键的值为 "1"，如果该键已经存在则返回 false，否则返回 true
        String uuid = UUID.randomUUID().toString();
        Boolean absent = redisTemplate.opsForValue().setIfAbsent("lock", "uuid", 30, TimeUnit.MILLISECONDS);
        // 如果 "lock" 键已经存在，则进入循环等待直到成功获取锁   加锁失败不能跳出方法，继续while循环
        while (!redisTemplate.opsForValue().setIfAbsent("lock", "1")) {
            TimeUnit.MILLISECONDS.sleep(10);
        }
        //走到这里说明加锁成功
        // redisTemplate.expire("lock", 10, TimeUnit.MINUTES);
        return uuid;
    }

    /**
     * 解锁方法，删除 Redis 中的锁
     */
    @Override
    public void unlock(String uuid) {

        //删锁： 获取值 + 删除锁 = 原子操作
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                "then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        //删锁成功：
        //1、返回0： 删锁失败，有可能是别人的锁
        //2、返回1： 删锁成功

        /**
         * 脚本无论多长都是一个原子操作
         * "if redis.call(\"get\","lock") == uuid\n" +
         * "then\n" +
         * "    return redis.call(\"del\","lock")\n" +
         * "else\n" +
         * "    return 0\n" +
         * "end";
         *
         * 1、lua脚本保证原子性，但保证事务吗？
         * 2、lua脚本不要写超长而且还加各种修改，会导致事务问题
         * 3、开启 redis 事务机制。性能急剧下降
         */
        Long lock = redisTemplate.execute(
                new DefaultRedisScript<>(script, Long.class),
                Arrays.asList("lock"),
                uuid
        );
        System.out.println("删锁结果：" + lock);

    }
}

